import Dexie from 'dexie'
import { normalizeFileName } from '../utils'
import type { FileSystemShowDirectoryPickerOptions, FileSystemWindow } from '../types'
import { useActivation } from './useActivation'

export type RootHandlePickerFrom = 'history' | 'picker'

export interface UseDirectoryPickerOptions {
  database: string
  rootPickerOptions?: FileSystemOpenDirectoryOptions
  onRootHandlePicker?: (handle: FileSystemDirectoryHandle, from: RootHandlePickerFrom) => void
}

export interface WriteToFileHandleOptions {
  directory: FileSystemDirectoryHandle
  blob: Blob
  name: string
}

export interface WriteToFileSelectorOptions {
  directory: string
  directoryOptions: FileSystemOpenDirectoryOptions
  blob: Blob
  name: string
}

export type WriteToFileOptions = WriteToFileHandleOptions | WriteToFileSelectorOptions

export interface DirectoryKey {
  name: string
  fullName: string
  kind: FileSystemHandleKind
  postfix?: string
}

export interface FileSystemOpenDirectoryOptions extends FileSystemShowDirectoryPickerOptions {
  create?: boolean
}

export type FsExistParseNameResult = Pick<DirectoryKey, 'name'|'postfix'|'fullName'>

const postfixRegExp = /\.[a-zA-Z0-9]+$/

function parseName(fullName: string): FsExistParseNameResult {
  const [postfix] = fullName.match(postfixRegExp) ?? []
  const name = postfix ? fullName.replace(postfixRegExp, '') : fullName
  return { name, postfix, fullName }
}

export function useDirectoryPicker(options: UseDirectoryPickerOptions) {
  let rootHandle: FileSystemDirectoryHandle | null = null
  const rootHandleId = options.rootPickerOptions?.id || `${options.database}_root_handle`

  const onRootHandlePicker = options.onRootHandlePicker ?? (() => {})

  const db = new Dexie(options.database)
  db.version(1).stores({
    handle: 'id, handle',
  })
  const table = db.table('handle')
  table.get({ id: rootHandleId }).then(async (data) => {
    if (data) {
      await requestPermission(data.handle)
      rootHandle = data.handle
      onRootHandlePicker(rootHandle!, 'history')
    }
  })

  async function requestPermission(handle: FileSystemHandle) {
    const state = await handle.queryPermission({ mode: 'readwrite' })
    if (state === 'prompt') {
      await useActivation(() => handle.requestPermission({ mode: 'readwrite' }))
    }
  }

  async function showPicker(pickerOptions?: FileSystemShowDirectoryPickerOptions, isRoot?: boolean) {
    const handle = await (window as unknown as FileSystemWindow).showDirectoryPicker(pickerOptions)

    if (isRoot) {
      rootHandle = handle
      onRootHandlePicker(rootHandle!, 'picker')
    }

    const id = isRoot ? rootHandleId : pickerOptions?.id ?? options.rootPickerOptions?.id
    if (id) {
      table.put({ id, handle: handle })
    }

    return handle
  }

  function showRootPicker(pickerOptions?: FileSystemShowDirectoryPickerOptions) {
    return showPicker(pickerOptions ?? options.rootPickerOptions, true)
  }

  async function openDirectory(
    name: string,
    options?: FileSystemOpenDirectoryOptions
  ): Promise<FileSystemDirectoryHandle | undefined> {
    let directory: FileSystemDirectoryHandle | undefined = (await table.get({ id: name }))?.handle
    if (directory) {
      await requestPermission(directory)
    } else {
      const create = options?.create
      if (rootHandle) {
        directory = await rootHandle.getDirectoryHandle(name, { create })
      } else if (create) {
        directory = await showPicker(options)
      }

      if (directory) {
        table.put({ id: name, handle: directory })
      }
    }

    return directory
  }

  async function closeDirectory(name: string) {
    await table.delete(name)
  }

  async function getDirectoryKeys(
    directory: string | FileSystemDirectoryHandle,
    options?: FileSystemOpenDirectoryOptions
  ) {
    const keyList: DirectoryKey[] = []
    try {
      let newDirectory: FileSystemDirectoryHandle | undefined = undefined
      if (typeof directory === 'string') {
        newDirectory = await openDirectory(directory, options)
      } else {
        newDirectory = directory
      }
      if (newDirectory) {
        for await (const [fullName, handle] of newDirectory.entries()) {
          const { kind } = handle
          if (kind === 'file') {
            const { name, postfix } = parseName(fullName)
            keyList.push({ kind, fullName, name, postfix })
          } else {
            keyList.push({ kind, fullName, name: fullName })
          }
        }
      }
      return keyList
    } catch (error) {
      console.error(error)
      return keyList
    }
  }

  async function writeToLocal(options: WriteToFileOptions) {
    let { directory } = options
    if (typeof directory === 'string') {
      const { directoryOptions } = options as WriteToFileSelectorOptions
      if (!directoryOptions.create) directoryOptions.create = true
      directory = (await openDirectory(directory, directoryOptions))!
    }

    const name = normalizeFileName(options.blob, options.name)
    const fileHandle = await directory.getFileHandle(name, { create: true })
    const writable = await fileHandle.createWritable({ mode: 'exclusive' })
    await writable.write(options.blob)
    await writable.close()
  }

  async function existChild(type: FileSystemHandleKind, childValidator: (data: FsExistParseNameResult) => boolean) {
    if (rootHandle) {
      for await (const [fullName, handle] of rootHandle.entries()) {
        if (handle.kind === type && childValidator(parseName(fullName))) {
          return true
        }
      }
      return false
    }
    return Promise.reject('未选择目标目录')
  }

  return {
    showPicker,
    showRootPicker,
    openDirectory,
    closeDirectory,
    getDirectoryKeys,
    writeToLocal,
    existChild,
    getRootHandle() {
      return rootHandle
    },
  }
}
